/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.edp.caf.tenancy.core.extensions;


import com.zaxxer.hikari.HikariDataSource;
import io.iec.edp.caf.commons.transaction.JpaTransaction;
import io.iec.edp.caf.commons.transaction.TransactionPropagation;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import io.iec.edp.caf.tenancy.api.ITenantService;
import io.iec.edp.caf.tenancy.api.entity.DbConnectionInfo;
import io.iec.edp.caf.tenancy.api.event.DataSourceProcessingEvent;
import io.iec.edp.caf.tenancy.core.event.DataSourceProcessingEventPublisher;
import io.iec.edp.caf.tenancy.core.utils.HelperTools;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 这个类负责根据租户ID来提供对应的数据源
 * @author lanyuanxiaoyao
 * @version 1.0
 */
@Slf4j
public class TenantDataSourceProvider {

    /**
     * 使用一个map来存储我们租户和对应的数据源，租户和数据源的信息就是从我们的tenant_info表中读出来
     */
    private volatile static ConcurrentHashMap<String, DataSource> dataSourceMap = new ConcurrentHashMap<>();

    /**
     * 使用一个map来存储我们租户和对应的数据源，租户和数据源的信息就是从我们的tenant_info表中读出来
     */
    private volatile static ConcurrentHashMap<String, DataSource> connectionMap = new ConcurrentHashMap<>();

    /**
     * 使用一个map来存储租户、su对应的Hikari的连接
     */
    private volatile static ConcurrentHashMap<String, DataSource> hikariConnectionMap = new ConcurrentHashMap<>();
    /**
     *  tenantIdentifier采用三段式命名 tid,appcode,su
     */
    static final int tenantIdentifierSegments3 = 3;

    private static Lock locker = new ReentrantLock();

    /**
     *  主库数据源id
     */
    public static final String MASTERDB = "5e4a6760-9cae-4ed6-a9f9-af5a53f56b7a";

    /**
     *  租户服务
     */
    static ITenantService tenantService = null;


    /**
     * @Description: 根据传进来的tenantIdentifier决定返回的数据源
     * @param tenantIdentifier 多租户数据库表示符
     */
    public static DataSource getTenantDataSource(String tenantIdentifier) {
        if (tenantIdentifier.equals(MASTERDB)) {
            return dataSourceMap.get(MASTERDB);
        }
        String[] tis = tenantIdentifier.split(",");

        DataSource ds = dataSourceMap.get(tenantIdentifier);
        if (ds == null) {
            try {
                locker.lock();
                if (dataSourceMap.containsKey(tenantIdentifier) == false) {
                    tenantService = SpringBeanUtils.getBean(ITenantService.class);
                    DbConnectionInfo dbConnectionInfo = null;
                    //存在事务时，必须进行事务隔离，避免死循环（事务环境只走一次resolveTenantIdentifier）
                    if(TransactionSynchronizationManager.isSynchronizationActive()) {
                        JpaTransaction tran = JpaTransaction.getTransaction();
                        try {
                            tran.begin(TransactionPropagation.REQUIRES_NEW);
                            dbConnectionInfo = tenantService.getDBConnInfo(Integer.parseInt(tis[0]), tis[1], tis[2]);
                            tran.commit();
                        } catch (Throwable e) {
                            tran.rollback();
                            throw e;
                        }
                    }else{
                        dbConnectionInfo = tenantService.getDBConnInfo(Integer.parseInt(tis[0]), tis[1], tis[2]);
                    }

                    if (dbConnectionInfo == null) {
                        log.info(String.format("根据租户标识：%s 未取到数据库连接信息，默认返回主库数据源，请求URL:%s！", tenantIdentifier, getRequestUrl()));
                        ds = dataSourceMap.get(MASTERDB);
                        hikariConnectionMap.putIfAbsent(tenantIdentifier,ds);
                        dataSourceMap.putIfAbsent(tenantIdentifier, ds);
                    } else {
                        log.info(String.format("根据租户标识：%s 已取到数据库连接信息，数据源编号:%s", tenantIdentifier, dbConnectionInfo.getId()));
                        log.info(tenantIdentifier+"对应数据库连接："+dbConnectionInfo.getConnectionString());
                        ds = connectionMap.get(dbConnectionInfo.getId());
                        if(ds ==null) {
                            ds = HelperTools.toDataSource(dbConnectionInfo);
                            hikariConnectionMap.putIfAbsent(tenantIdentifier,ds);
                            ds = handleDatasource(ds);
                            dataSourceMap.putIfAbsent(tenantIdentifier, ds);
                            connectionMap.putIfAbsent(dbConnectionInfo.getId(), ds);
                        }else {
                            dataSourceMap.putIfAbsent(tenantIdentifier, ds);
                        }
                    }
                }
            } catch (Exception ex) {
                log.warn(String.format("根据租户标识确定数据源时出错，默认返回主库数据源！租户标识：%s;异常信息：%s，", tenantIdentifier, ex.toString()));
                throw ex;
            } finally {
                locker.unlock();
            }
        }
        return dataSourceMap.get(tenantIdentifier);
    }

    public static HikariDataSource getHikariDataSource(String tenantIdentifier){
        try{
            DataSource ds = hikariConnectionMap.get(tenantIdentifier);
            if(ds!=null) return (HikariDataSource)ds;
            else return null;
        }catch (Throwable e){
            throw new RuntimeException("无法获取hikari数据源：",e);
        }

    }

    private static String getRequestUrl() {
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (servletRequestAttributes != null) {
            HttpServletRequest request = servletRequestAttributes.getRequest();
            if (request == null ||
                    request.getScheme()==null ||
                    request.getRequestURL()==null)
                return "";
            else
                return request.getRequestURL().toString();
        }
        return "";
    }

    /**
     * @Description: 初始化的时候用于添加数据源的方法
     * @param tenantIdentifier 多租户数据库表示符
     * @param dataSource 数据连接信息
     */
    public static void addDataSource(String tenantIdentifier, DataSource dataSource) {
        dataSource = handleDatasource(dataSource);
        dataSourceMap.putIfAbsent(tenantIdentifier, dataSource);
    }


    public static DataSource handleDatasource(DataSource dataSource){
        //触发事件
        DataSourceProcessingEventPublisher eventPublisher = SpringBeanUtils.getBean(DataSourceProcessingEventPublisher.class);
        if(eventPublisher!=null) {
            DataSourceProcessingEvent event = new DataSourceProcessingEvent(dataSource);
            eventPublisher.onDataSourceProcessing(event);
            dataSource = event.getDataSource();
        }
        return dataSource;
    }

    public static void stop()
    {
        if ( dataSourceMap != null ) {
            dataSourceMap.clear();
            dataSourceMap = null;
        }
    }
}
